-- dcache wrapper
-- this is provided for a place holder until you do the cache labs
-- until then you should just place this file between your memory stage
-- of your pipeline and your priority mux for main memory.
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
use ieee.std_logic_arith.all;

entity dcache is
  port(
    clk            : in  std_logic;
    memclk         : in  std_logic;
    nReset         : in  std_logic;
    
    dMemRead       : in  std_logic;                       -- CPU side
    dMemWrite      : in  std_logic;                       -- CPU side
    dMemWait       : out std_logic;                       -- CPU side
    dMemAddr       : in  std_logic_vector (31 downto 0);  -- CPU side
    dMemDataRead   : out std_logic_vector (31 downto 0);  -- CPU side
    dMemDataWrite  : in  std_logic_vector (31 downto 0);  -- CPU side

    adMemRead      : out std_logic;                       -- arbitrator side
    adMemWrite     : out std_logic;                       -- arbitrator side
    --adMemWait      : in  std_logic;                       -- arbitrator side
    adMemAddr      : out std_logic_vector (31 downto 0);  -- arbitrator side
    adMemDataRead  : in  std_logic_vector (31 downto 0);  -- arbitrator side
    adMemDataWrite : out std_logic_vector (31 downto 0);   -- arbitrator side
    
    state1         : out std_logic_vector (1 downto 0);
    state2         : out std_logic_vector (3 downto 0)
    );

end dcache;

architecture struct of dcache is
	type tagbits is array (0 to 127) of std_logic;
	type physicalAddr is array (0 to 127) of std_logic_vector(20 downto 0);
	type dcachedata is array (0 to 127) of std_logic_vector(127 downto 0);
	type fsmstatetype is (srw, sdww, sdrw);
	type memstatetype is (ss, srw0, srw1, srw2, srw3, sww0, sww1, sww2, sww3, sidle);
	signal refillfsm: memstatetype := sidle;
	signal cachefsm: fsmstatetype := srw;
	signal cachebusy: std_logic;
	signal resetrefillfsm: std_logic;
	signal valid: tagbits;
	signal dirty: tagbits;
	signal hit: std_logic;
	signal pa: physicalAddr;
	signal data: dcachedata;
	signal cacheline: integer   :=0;
	signal cachecolumn: integer :=0;
	signal writeback: std_logic;
	signal skipcolumn: std_logic;
	signal dWriteBackAddr: std_logic_vector(31 downto 4);
	signal directwrite: std_logic := '0';

begin
	cacheline <= conv_integer(dMemAddr(10 downto 4));
	cachecolumn <= conv_integer(dMemAddr(3 downto 2));
	dWriteBackAddr <= pa(cacheline) & dMemAddr(10 downto 4);	
	with cachefsm select 
	   state1 <= "00" when srw,
	            "01" when sdww,
	            "10" when sdrw;
	with refillfsm select
	   state2 <= "0000" when ss,
  	              "0001" when sww0,
	              "0010" when sww1,
	              "0011" when sww2,
	              "0100" when sww3,
	              "0101" when srw0,
	              "0110" when srw1,
	              "0111" when srw2,
	              "1000" when srw3,
	              "1001" when sidle;
	hit <= '1' when valid(cacheline) = '1' and pa(cacheline) =  dMemAddr (31 downto 11) else '0';
	state_cachefsm: process(clk)
	begin
		if (rising_edge(clk)) then
			directwrite <= '0';
			if (nReset = '0') then
					-- Reset here
				for i in 0 to 127 loop
					valid(i) <= '0';
					dirty(i) <= '0';
				end loop;
				cachefsm <= srw;
				resetrefillfsm <= '0';				
			else
			case cachefsm is
				when srw =>					
					if (dMemWrite = '1') then
					   skipcolumn <= '1';
					   if hit='1' then
    				      dMemWait <= '0';
					      directwrite <= '1';
					      cachefsm <= srw;
					      dMemDataRead <= x"00000000";
					      dirty(cacheline) <= '1';
						  valid(cacheline) <= '1';
					   elsif valid(cacheline)='0' or dirty(cacheline)='0' then					      
						  dMemWait <= '1';
					      writeback <= '0';
					      resetrefillfsm <= '1';
					      cachefsm <= sdww;
					      dMemDataRead <= x"00000000";
					   else
						  dMemWait <= '1';
					      writeback <= '1';
					      resetrefillfsm <= '1';
					      cachefsm <= sdww;
					      dMemDataRead <= x"00000000";
					   end if;
					elsif (dMemRead = '1') then
					  skipcolumn <= '0';				   
				      if hit = '1' then
				        --hit
					        dMemWait <= '0';
					        case cachecolumn is
						        when 0 => dMemDataRead <= data(cacheline)(31 downto 0);
						        when 1 => dMemDataRead <= data(cacheline)(63 downto 32);
						        when 2 => dMemDataRead <= data(cacheline)(95 downto 64);
						        when others => dMemDataRead <= data(cacheline)(127 downto 96);
					        end case;	
					        cachefsm <= srw;				
					  else
					        dMemWait <= '1';
					        cachefsm <= sdrw;
					        resetrefillfsm <= '1';
					        dMemDataRead <= x"00000000";
					        if (dirty(cacheline)='1') then writeback <= '1';
					        else writeback <= '0';
					        end if;
				      end if;				
					end if;					
				when sdww =>
					resetrefillfsm <= '0';
					if cachebusy = '1' then
					    writeback <= '1';
                        cachefsm <= sdww;
                        dMemDataRead <= x"00000000";
					else --writeback finished					    
                   --set up tags
					    pa(cacheline) <= dMemAddr(31 downto 11);
					    valid(cacheline) <= '1';
					    dirty(cacheline) <= '0';
					    --reset state
					    dMemWait <= '0';
					    cachefsm <= srw;
					    writeback <= '0';
					end if;
				when sdrw =>
					resetrefillfsm <= '0';
					if cachebusy = '1' then
					    dMemWait <= '1';
						 cachefsm <= sdrw;
						 dMemDataRead <= x"00000000";
					else
					   --set up tags
						pa(cacheline) <= dMemAddr(31 downto 11);
						valid(cacheline) <= '1';		
						dirty(cacheline) <= '0';				
						--output data
						case cachecolumn is
							when 0 => dMemDataRead <= data(cacheline)(31 downto 0);
							when 1 => dMemDataRead <= data(cacheline)(63 downto 32);
							when 2 => dMemDataRead <= data(cacheline)(95 downto 64);
							when others => dMemDataRead <= data(cacheline)(127 downto 96);
						end case;
						--reset state
						dMemWait <= '0';
						cachefsm <= srw;
						writeback <= '0';
					end if;
			end case;
			end if;
		end if;
	end process;
	
	state_memfsm: process(directwrite, resetrefillfsm, memclk)
	begin
	   if (nReset = '0') then 
			cachebusy <= '0';
			refillfsm <= sidle;
	   elsif (directwrite = '1') then
	       cachebusy <= '0';
	       refillfsm <= sidle;
	       case cachecolumn is
				when 0 => data(cacheline)(31 downto 0) <= dMemDataWrite;
				when 1 => data(cacheline)(63 downto 32) <= dMemDataWrite;
				when 2 => data(cacheline)(95 downto 64) <= dMemDataWrite;
				when others => data(cacheline)(127 downto 96) <= dMemDataWrite;
		   end case;
		elsif (resetrefillfsm = '1') then
			cachebusy <= '1';
			refillfsm <= ss;
		elsif (rising_edge(memclk)) then
			case refillfsm is
				when ss =>
					cachebusy <= '1';					
					if (writeback = '1') then
					    adMemAddr(31 downto 0) <= dMemAddr(31 downto 4)&"0000";
					    adMemDataWrite <= data(cacheline)(31 downto 0);
					    adMemRead <= '0';
					    adMemWrite<= '1';					    
					    refillfsm <= sww0;
					else
					    adMemRead <= '1';
					    adMemWrite<= '0';
						if (skipcolumn = '1' and cachecolumn = 0) then
							data(cacheline)(31 downto 0) <= dMemDataWrite;
							adMemAddr(31 downto 0) <= dMemAddr(31 downto 4)&"0100";
							refillfsm <= srw1;
						else
							adMemAddr(31 downto 0) <= dMemAddr(31 downto 4)&"0000";
							refillfsm <= srw0;
						end if;							
				    end if;				
				when sww0 =>
  			       cachebusy <= '1';
				   adMemRead <= '0';
  			       adMemWrite<= '1';
				   adMemAddr(31 downto 0) <= dWriteBackAddr &"0100";
				   adMemDataWrite <= data(cacheline)(63 downto 32);
				   refillfsm <= sww1;
				when sww1 =>
				   cachebusy <= '1';
				   adMemRead <= '0';
  			       adMemWrite<= '1';
				   adMemAddr(31 downto 0) <= dWriteBackAddr &"1000";
				   adMemDataWrite <= data(cacheline)(95 downto 64);
				   refillfsm <= sww2;
				when sww2 =>
				   cachebusy <= '1';
				   adMemRead <= '0';
  			       adMemWrite<= '1';
				   adMemAddr(31 downto 0) <= dWriteBackAddr &"1100";
				   adMemDataWrite <= data(cacheline)(127 downto 96);				   
				   refillfsm<= sww3;					   		   
				when sww3 =>
					cachebusy <= '1';				    					                 
					adMemRead <= '1';
					adMemWrite<= '0';					    
					if cachecolumn = 0 then
						data(cacheline)(31 downto 0) <= dMemDataWrite;
						adMemAddr(31 downto 0) <= dMemAddr(31 downto 4)&"0100";
						refillfsm <= srw1;
					else
			 			adMemAddr(31 downto 0) <= dMemAddr(31 downto 4)&"0000";
						refillfsm <= srw0;			
					end if;
				when srw0 =>
					cachebusy <= '1';
					data(cacheline)(31 downto 0) <= adMemDataRead;										
					adMemRead <= '1';
					adMemWrite <= '0';
					if skipcolumn = '1' and cachecolumn = 1 then
						adMemAddr(31 downto 0) <= dMemAddr(31 downto 4)&"1000";
						refillfsm <= srw2;
						data(cacheline)(63 downto 32) <= dMemDataWrite;
					else
						adMemAddr(31 downto 0) <= dMemAddr(31 downto 4)&"0100";
						refillfsm <= srw1;
					end if;
				when srw1 =>					
				    cachebusy <= '1';
					data(cacheline)(63 downto 32) <= adMemDataRead;									
					adMemRead <= '1';
					adMemWrite <= '0';
					if skipcolumn = '1' and cachecolumn = 2 then
						adMemAddr(31 downto 0) <= dMemAddr(31 downto 4)&"1100";
						refillfsm <= srw3;
						data(cacheline)(95 downto 64) <= dMemDataWrite;
					else
						adMemAddr(31 downto 0) <= dMemAddr(31 downto 4)&"1000";
						refillfsm <= srw2;
					end if;
				when srw2 =>				    
					data(cacheline)(95 downto 64) <= adMemDataRead;										
					adMemWrite <= '0';					
					if skipcolumn = '1' and cachecolumn = 3 then
						cachebusy <= '0';
						adMemRead <= '0';
						refillfsm <= sidle;
						data(cacheline)(127 downto 96) <= dMemDataWrite;
					else
						cachebusy <= '1';
						adMemRead <= '1';
						adMemAddr(31 downto 0) <= dMemAddr(31 downto 4)&"1100";
						refillfsm <= srw3;
					end if;
				when srw3 =>					
					data(cacheline)(127 downto 96) <= adMemDataRead;					
					adMemRead <= '0';
					adMemWrite<= '0';
					cachebusy <= '0';
					refillfsm <= sidle;					
				when sidle =>
					cachebusy <= '0';
					refillfsm <= sidle;
			end case;
		end if;
	end process;
end struct;
